﻿using System;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using lctdevice;
using EC_6000_Motion_Assistant.SDK;

namespace M60fanlichengxu
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        #region 全局变量
        /// <summary>
        /// 从站资源
        /// </summary>
        short DiNum = 0;   //Di数量
        short DoNum = 0;   //Do数量
        short AiNum = 0;   //Ai数量
        short AoNum = 0;   //Ao数量
        short SlaveNum = 0;//从站数量
        short ServoNum = 0;//伺服数量        

        /// <summary>
        /// 轴状态数组
        /// </summary>
        int[] ServoSts = null;         //接收 伺服轴的运行状态：M_GetSts
        int[] DriverPos = null;        //接收 驱动器内部的实际位置:M_ReadActualPosition
        double[] CmdPos = null;        //接收 指令（规划）位置：M_GetCmd
        double[] CmdVel = null;        //接收 指令（规划）速度：M_GetCmdVel
        double[] ServoVel = null;      //接收 轴的实际速度（即编码器的反馈速度）：M_GetEncVel
        double[] ServoPosition = null; //接收 轴的实际位置（即编码器的反馈位置）：M_GetEncPos
              
        /// <summary>
        /// 点位运动参数
        /// </summary>
        ecat_motion.CmdPrm CmdPrm = new ecat_motion.CmdPrm();//实例结构体“CmdPrm”，其中包括 acc 、dec 和 sTime 共3个字段
        int PositiveSoftLimit = 0;                           //正向软极限设置
        int NegativeSoftLimit = 0;                           //负向软极限设置
        uint InpBand = 0;                                    //运动完成判断范围
        uint InpTime = 0;                                    //运动完成停止时间
        double StopDec = 0;                                  //停止加速度
        double EmgDec = 0;                                   //急停加速度
        
        /// <summary>
        /// 回零运动参数
        /// </summary>   
        int OffSet = 0;                //回零完成后的偏移距离
        uint Speed1 = 0;               //第一回零速度
        uint Speed2 = 0;               //第二回零速度
        uint HomeAcc = 0;              //回零加速度
        short HomeMode = 1;            //回零模式，初始化等于1
        ushort ProbeFunction = 0;      //预留，默认为 0
        string[] AxisHomeStatus = null;//轴的回零状态（客户可以不定义）

        /// <summary>
        /// 插补运动
        /// </summary>
        int[] Position = null;           //接收 参与插补运动轴号对应的目标位置
        short[] InterpolationAxis = null;//接收 参与插补运动轴的轴号        

        /// <summary>
        /// 连续插补设置
        /// </summary>
        ecat_motion.CrdCfg crdCfg = new ecat_motion.CrdCfg();//实例CrdCfg结构体（用于设置连续插补参数）
        int sSpace = 0;                                      //FIFO剩余空间（sSpace + sCmdNum = 512）
        short sSts = 0;                                      //Buffer状态（1表示运动中，0表示停止状态）
        short sCmdNum = 0;                                   //FIFO里存在的点位数（未到达点位的数量）
        short fifo_0 = 0;                                    //FIFO :0,1
        short fifo_1 = 1;                                    //FIFO :0,1（本范例只使用了编号为0的fifo寄存器，故“fifo_1”变量从未使用过）
        short crd_1 = 1;                                     //坐标系编号:1
        short crd_2 = 2;                                     //坐标系编号:2（本范例只使用了编号为1的插补系，故“crd_2”变量从未使用过）
        double[] asv = new double[UInt16.MaxValue * 204];    //速度前瞻运算使用的内存空间

        /// <summary>
        /// 用到的线程
        /// </summary>
        public Thread Thread_EmgStatus = null; //获取板卡 急停 信号
        public Thread Thread_AxisStatus = null;//获取 轴 状态
        public Thread Thread_IoStatus = null;  //获取 IO 状态

        /// <summary>
        /// 接收IO信号的变量（本范例为了演示功能声明了如下6个用于接收IO信号的变量）
        /// </summary>
        uint Di1ToDi32Value = 0; //接收 数字量输入1～32通道的输入值，该变量值的 bit0位 对应 起始通道，bit31位 对应 第（起始值+31）通道
        uint Do1ToDo32Value = 0; //接收 数字量输出1～32通道的输出值，该变量值的 bit0位 对应 起始通道，bit31位 对应 第（起始值+31）通道
        short Di1Value = 0;      //接收 数字量输入1号通道的状态值
        short Do1Value = 0;      //接收 数字量输出1号通道的状态值
        short[] AiValue = null;  //接收 若干个模拟量输入通道的输入值
        short[] AoValue = null;  //接收 若干个模拟量输出通道的输出值

        /// <summary>
        /// 其他
        /// </summary>
        short selectAxis = 1;    //选中轴的轴号,默认是1
        short CardID = 0;        //M60板卡的卡号（从0开始）
        short CmpNo = 1;         //这里的1指的是从主站（M60板卡）出发，遇到的第1个LCTE-MINI-CMP模块
        uint tempXFifo = 0;      //用于执行“_2DCmp_GetTrigLTCPoint方法（获取2D位置比较触发当前触发锁存点位）”的中间变量
        short EmgFlag = 0;       //急停信号
        short OperatingMode = 0; //驱动器的运行模式
        #endregion

        private void IoStatusLoop()// 1 获取IO的状态 →“Thread_IoStatusLoop”线程
        {
            while (true)
            {
                Thread.Sleep(10);
                if (DiNum > 0)
                {
                    //使用 Port 方式一次可以获取32个通道的状态(第一个参数“1”表示起始通道号为1，这样就可以一次性获取1～32共32个通道的状态)
                    ecat_motion.M_Get_Digital_Port_Input( 1, ref Di1ToDi32Value, CardID);

                    //使用 Chn 方式一次只能获取1个通道的状态（第一个参数“1”表示要获取DI的第1通道的状态）                    
                    ecat_motion.M_Get_Digital_Chn_Input( 1, out Di1Value, CardID);
                }

                if (DoNum > 0)
                {
                    //使用 Port 方式一次可以获取32个通道的状态
                    ecat_motion.M_Get_Digital_Port_Output( 1, ref Do1ToDo32Value, CardID);

                    //使用 Chn 方式一次只能获取1个通道的状态                    
                    ecat_motion.M_Get_Digital_Chn_Output( 1, out Do1Value, CardID);
                }

                if (AiNum > 0)
                {
                    //获取模拟量的输入值
                    ecat_motion.M_Get_Analog_Input( 1, out AiValue[0], AiNum, CardID);
                }

                if (AoNum > 0)
                {
                    //获取模拟量的输出值
                    ecat_motion.M_Get_Analog_Output( 1, out AoValue[0], AoNum, CardID);
                }
            }
        }

        private void EmgStatusLoop()// 2 获取板卡Emg状态 →“Thread_EmgStatusLoop”线程
        {
            while (true)
            {
                Thread.Sleep(100);
                ecat_motion.M_GetEmg( ref EmgFlag, CardID);//板卡EMG信号状态: EmgFlag == 1表示急停中（保持信号），EmgFlag == 0 表示急停动作未触发。                                
            }
        }
       
        private void AxisStatusLoop()// 3 获取轴的状态信息 →“Thread_AxisStatusLoop”线程
        {
            while (true)
            {
                Thread.Sleep(10);
                if (ServoNum > 0)
                {
                    //获取轴状态
                    ecat_motion.M_GetSts(1, out ServoSts[0], ServoNum, CardID);

                    //获取编码器位置
                    ecat_motion.M_GetEncPos(1, out ServoPosition[0], ServoNum, CardID);

                    //获取编码器速度
                    ecat_motion.M_GetEncVel(1, out ServoVel[0], ServoNum, CardID);

                    //获取轴规划位置
                    ecat_motion.M_GetCmd(1, out CmdPos[0], ServoNum, CardID);

                    //获取轴规划速度
                    ecat_motion.M_GetCmdVel(1, out CmdVel[0], ServoNum, CardID);

                    //获取驱动器内部实际位置
                    ecat_motion.M_ReadActualPosition(1,out DriverPos[0], ServoNum, CardID);
                }
            }
        }
               
        private void UiTimer_Tick(object sender, EventArgs e)//UI计时器,刷新UI界面上控件的显示数据
        {
            #region UI显示板卡EMG状态
            if ( EmgFlag == 1 )//板卡 EMG 信号状态，1：急停中（保持信号），0：未急停
                btnResetEmg.BackColor = Color.Red;
            else
                btnResetEmg.BackColor = Color.White;
            #endregion

            #region UI显示选中轴的状态
            if (ServoNum > 0)//“ServoNum > 0”即从站资源中存在轴
            {
            //*****UI显示 选中轴 的指令（规划）位置、速度 和 编码器（当前）位置、速度*****//⬇
                TextBox_CmdPos.Text = CmdPos[ selectAxis - 1].ToString("f0");
                TextBox_CmdVel.Text = CmdVel[ selectAxis - 1].ToString("f0");
                TextBox_EncPos.Text = ServoPosition[ selectAxis - 1].ToString("f0");
                TextBox_EncVel.Text = ServoVel[ selectAxis - 1].ToString("f0");
                txtDriverPostion.Text = DriverPos[selectAxis - 1].ToString("f0");
            //****************************************************************************//⬆


                //************************ UI显示 选中轴 的状态 ******************************//⬇              
                //轴的状态字每一个Bit位代表的含义请参考PCIe-M60函数手册里的“M_GetSts()”方法

                //伺服报警 alarm
                if ((ServoSts[selectAxis - 1] & 0x02) == 0x02)
                    label_Alarm.BackColor = Color.Red;
                else
                    label_Alarm.BackColor = Color.Black;

                //原点信号 origin
                if ((ServoSts[selectAxis - 1] & 0x100000) == 0x100000)
                    label_Origin.BackColor = Color.Red;
                else
                    label_Origin.BackColor = Color.Black;

                //正极限 postive limit
                if ((ServoSts[selectAxis - 1] & 0x20) == 0x20)
                    label_Pel.BackColor = Color.Red;
                else
                    label_Pel.BackColor = Color.Black;

                //负极限 negative limit
                if ((ServoSts[selectAxis - 1] & 0x40) == 0x40)
                    label_Mel.BackColor = Color.Red;
                else
                    label_Mel.BackColor = Color.Black;

                //伺服使能 servo on
                if ((ServoSts[selectAxis - 1] & 0x200) == 0x200)
                    label_SrevoOn.BackColor = Color.Red;
                else
                    label_SrevoOn.BackColor = Color.Black;

                //运动 motion
                if ((ServoSts[selectAxis - 1] & 0x400) == 0x400)
                    label_Motion.BackColor = Color.Red;
                else
                    label_Motion.BackColor = Color.Black;

                //轴到位 inp
                if ((ServoSts[selectAxis - 1] & 0x800) == 0x800)
                    label_INP.BackColor = Color.Red;
                else
                    label_INP.BackColor = Color.Black;

                //回零错误 homing error
                if ((ServoSts[selectAxis - 1] & 0x10000) == 0x10000)
                    label_HomeError.BackColor = Color.Red;
                else
                    label_HomeError.BackColor = Color.Black;

                //回零完成 homing attained
                if ((ServoSts[selectAxis - 1] & 0x20000) == 0x20000)
                    label_HomeAttained.BackColor = Color.Red;
                else
                    label_HomeAttained.BackColor = Color.Black;
            //**************************************************************************//⬆

            //************************获取选中轴的运行模式******************************//⬇
                ecat_motion.M_EcatGetOperationMode(selectAxis, ref OperatingMode, 0);//获取选中轴的运行模式（8为位置模式，6为回零模式，10为转矩模式）
                txtShowOperatingMode.Text = OperatingMode.ToString();
            //**************************************************************************//⬆

            //************************显示选中轴的回零状态******************************//⬇
                TextBox_HomeStatus.Text = AxisHomeStatus[selectAxis - 1];
            //**************************************************************************//⬆
            }
            #endregion

            #region UI显示IO的状态
            if (tabControl1.SelectedTab.Name == "tabPage6")
            {
                if (DiNum > 0)
                {
                    TextBox_Di1To32Value.Text = Di1ToDi32Value.ToString();
                    TextBox_Di1Value.Text = Di1Value.ToString();
                }
                if (DoNum > 0)
                {
                    TextBox_Do1To32Value.Text = Do1ToDo32Value.ToString();
                    TextBox_Do1Value.Text = Do1Value.ToString();
                }
                if (AiNum > 0)
                    TextBox_Ai1Value.Text = AiValue[0].ToString();
                if (AoNum > 0)
                    TextBox_Ao1Value.Text = AoValue[0].ToString();
            }
            #endregion

            #region UI显示连续插补时Buffer的状态
            if (ServoNum > 1 && tabControl1.SelectedTab.Name == "tabPage4")//从站中存在轴的数量至少为2的情况下（并且鼠标点开了"tabPage4"）才刷新BufferCmdNum和BufferSpace的信息
            {
                ecat_motion.M_CrdStatus( crd_1, out sSts, out sCmdNum, out sSpace, fifo_0, CardID);//获取缓冲区运行状态
                Txt_BufferCmdnum.Text = "已经压入的点数: " + sCmdNum;
                Txt_BufferSpace.Text = "剩余可用的点数: " + sSpace;
            }
            #endregion

            #region UI显示CMP模块各通道的状态
            if (tabControl1.SelectedTab.Name == "tabPage7")
            {
                short CmpNo = 1;//这里的1指的是从主站（M60板卡）出发，遇到的第1个LCTE-MINI-CMP模块

                //编码器输入通道部分⬇
                int[] EncoderData = new int[4];
                for (int i = 0; i < 4; i++)
                {
                    Mini_Cmp_Lib.Encoder_GetEncoderData(CardID, CmpNo, i, ref EncoderData[i]);//获取指定编码器输入通道的坐标
                }
                TextBox_Encoder0.Text = EncoderData[0].ToString();
                TextBox_Encoder1.Text = EncoderData[1].ToString();
                TextBox_Encoder2.Text = EncoderData[2].ToString();
                TextBox_Encoder3.Text = EncoderData[3].ToString();

                //触发输出通道部分⬇
                uint[] TriggerCounter = new uint[4];
                for (int i = 0; i < 4; i++)
                {
                    Mini_Cmp_Lib.TriggerOut_GetCounter(CardID, CmpNo, i, ref TriggerCounter[i]);//获取指定触发输出通道的触发计数
                }
                TextBox_TriggerChannel0.Text = TriggerCounter[0].ToString();
                TextBox_TriggerChannel1.Text = TriggerCounter[1].ToString();
                TextBox_TriggerChannel2.Text = TriggerCounter[2].ToString();
                TextBox_TriggerChannel3.Text = TriggerCounter[3].ToString();
            }
            if (tabControl2.SelectedTab.Name == "tabPage12")
            {
                uint xFifo = 0, yFifo = 0;
                Mini_Cmp_Lib._2DCmp_GetTrigFifoCnt(0, CmpNo, ref xFifo, ref yFifo);//获取2D位置比较触发比较器FIFO计数
                txtXFifoCount.Text = xFifo.ToString();
                txtYFifoCount.Text = yFifo.ToString();
                if (xFifo != tempXFifo)
                {
                    int xltcp = 0, yltcp = 0;
                    Mini_Cmp_Lib._2DCmp_GetTrigLTCPoint(0, CmpNo, ref xltcp, ref yltcp);//获取2D位置比较触发当前触发锁存点位

                    txtXTrigLtcPoint.Text = xltcp.ToString();
                    txtYTrigLtcPoint.Text = yltcp.ToString();
                }
                tempXFifo = xFifo;
            }
            #endregion           
        }

        private void MainForm_Load(object sender, EventArgs e)//打开卡片和加载eni文件
        {
            //关闭卡片（针对上次没有正常关闭板卡的情况）
            try
            {
                ecat_motion.M_Close(CardID); 
            }
            catch{ }

            //打开卡片
            short Ret = ecat_motion.M_Open( CardID, 0);//第2个参数为预留参数，随意给值
            if (Ret == 0)
            {
                UiTimer.Enabled = true;

                Thread_EmgStatus = new Thread(EmgStatusLoop)//连接总线成功后，开启“Thread_EmgStatus”线程，用于刷新板卡Emg信号
                { IsBackground = true };
                Thread_EmgStatus.Start();                
            }
            else
            {
                MessageBox.Show("打开卡片失败，返回值：" + Ret);
                this.Close();
            }

            //加载ENI文件 
            Ret = ecat_motion.M_LoadEni("C:\\Program Files (x86)\\LCT\\PCIe-M60\\Eni\\eni.xml", CardID);   
            if (Ret != 0)
            {
                MessageBox.Show("加载ENI文件失败，返回值：" + Ret);
                this.Close();
            }
        }

        private void btnResetEmg_Click(object sender, EventArgs e)//重置板卡急停信号
        {
            short Ret = ecat_motion.M_ClrEmg(CardID);//重置M60板卡的急停信号
            AddLog("重置板卡急停信号", Ret);         
        }

        private void btnLoadParamentsFromFile_Click(object sender, EventArgs e)//从文件加载（配置）参数
        {
            //C:\Program Files (x86)\LCT\Pcie-M60\Motion_Assistant\AxisParam
            short Ret = ecat_motion.M_LoadParamFromFile("C:\\Program Files (x86)\\LCT\\PCIe-M60\\Motion_Assistant\\AxisParam\\ParamCard0.ini",CardID);
            AddLog("从文件加载参数", Ret);
            if (Ret == 0)
                btnLoadParamentsFromFile.BackColor = Color.Green;
        }

        private void btnConnect_Click(object sender, EventArgs e)//连接总线、获取从站资源
        {
            ecat_motion.M_ResetFpga( CardID);//重置FPGA芯片（针对EtherCAT总线连接超时的问题）
            Thread.Sleep(500);               //再延时500ms

            AddLog("连接总线中，请勿操作！");
            short Ret = ecat_motion.M_ConnectECAT( 1, CardID);//连接总线，option为设置断线输出保持的参数，0-不保持；1-保持 （注：连接总线需要一定的时间，所需时间会随着从站数量的增多而变长）
            AddLog("连接总线", Ret);
                
            if (Ret == 0)//如果返回值为0，即连接总线成功
            {
                btnConnect.BackColor = Color.Green;
                btnDisconnect.BackColor = Color.White;

                //获取从站资源
                ecat_motion.SL_RES Sl_res = new ecat_motion.SL_RES();    //实例ecat_motion.cs接口中用到的“SL_RES 结构体”
                Ret = ecat_motion.M_GetSlaveResource( out Sl_res, CardID);//获取Ethercat网络中的从站资源
                if (Ret == 0)//如果返回值为0，即获取从站资源成功
                {
                    SlaveNum = (short)Sl_res.SlaveNum;//变量 SlaveNum 接收 从站数量
                    Label_SlaveNum.Text = "从站数量:" + SlaveNum.ToString();
                    ServoNum = (short)Sl_res.ServoNum;//变量 ServoNum 接收 轴数量
                    Label_ServoNum.Text = "轴 数 量:" + ServoNum.ToString();
                    DiNum = (short)Sl_res.DiNum;      //变量 DiNum 接收 DI数量
                    Label_DiNum.Text = "Di 数 量:" + DiNum.ToString();
                    DoNum = (short)Sl_res.DoNum;      //变量 DoNum 接收 DO数量
                    Label_DoNum.Text = "Do 数 量:" + DoNum.ToString();
                    AiNum = (short)Sl_res.AiNum;      //变量 AiNum 接收 AI数量
                    Label_AiNum.Text = "Ai 数 量:" + AiNum.ToString();
                    AoNum = (short)Sl_res.AoNum;      //变量 AoNum 接收 AO数量
                    Label_AoNum.Text = "Ao 数 量:" + AoNum.ToString();                        

                    if (ServoNum > 0)//如果从站中存在轴(这里做一下判断，因为与板卡连接的从站中可以只有IO而没有轴)
                    {
                        //初始化与轴相关的数组⬇
                        ServoSts = new int[ServoNum];         //接收 轴的状态
                        ServoPosition = new double[ServoNum]; //接收 轴的（实际）位置
                        ServoVel = new double[ServoNum];      //接收 轴的（实际）速度
                        CmdPos = new double[ServoNum];        //接收 轴的规划（指令）位置
                        CmdVel = new double[ServoNum];        //接收 轴的规划（指令）速度
                        DriverPos = new int[ServoNum];        //接收 驱动器内部的实际位置
                        AxisHomeStatus = new string[ServoNum];//接收 轴的回零状态
                        for (int i = 0; i < ServoNum; i++)    //每个轴的回零状态初始化赋值为“轴X尚未执行回零命令”
                            AxisHomeStatus[i] = "轴"+(i+1)+"尚未执行回零命令";
                            
                        Thread_AxisStatus = new Thread(AxisStatusLoop)//开启“Thread_AxisStatus”线程，用于刷新轴的状态       
                        { IsBackground = true };                            
                        Thread_AxisStatus.Start();

                        nudSelectAxisID.Maximum = ServoNum;   // NumericUpDown_SelectAxisID 控件可选的最大轴号为从站资源中轴的数量
                        nudSelectAxisID.Minimum = 1;          // NumericUpDown_SelectAxisID 控件可选的最小轴号为1
                        nudSelectAxisID.Value = 1;            // NumericUpDown_SelectAxisID 控件默认选择的轴号为1
                    }
                        
                    if (( DiNum + DoNum + AiNum + AoNum ) > 0)//如果从站中存在IO(同理，这里做一下判断，因为与板卡连接的从站中可以只有轴而没有IO)
                    { 
                        //初始化AIO相关的数组                           
                        if ( AiNum > 0 )
                            AiValue = new short[AiNum];//初始化Ai数组                            
                        if ( AoNum > 0 )
                            AoValue = new short[AoNum];//初始化Ao数组
                            
                        Thread_IoStatus = new Thread(IoStatusLoop)//开启“Thread_IoStatus”线程，用于刷新IO的状态
                        { IsBackground = true };
                        Thread_IoStatus.Start();
                    }                    
                }                  
            }
            
        }

        private void btnDisconnect_Click(object sender, EventArgs e)//断开总线
        {
            try//关闭板卡Emg状态线程
            {
                Thread_EmgStatus.Abort(0);
                Thread.Sleep(100);
            }
            catch { }

            try//关闭轴状态线程
            {
                Thread_AxisStatus.Abort(0);
                Thread.Sleep(100);
            }
            catch { }

            try//关闭IO状态线程
            {
                Thread_IoStatus.Abort(0);
                Thread.Sleep(100);
            }
            catch { }

            try//所有轴去使能
            {
                for (short i = 1; i <= ServoNum; i++)
                    ecat_motion.M_Servo_Off(i, 0);
                Thread.Sleep(100);
            }
            catch { }

            short Ret = ecat_motion.M_DisconnectECAT(0);//断开总线段
            AddLog("断开总线", Ret);
            if (Ret == 0)//如果返回值为0，即断开总线成功
            {
                btnLoadParamentsFromFile.BackColor = Color.White;
                btnConnect.BackColor = Color.White;
                btnDisconnect.BackColor = Color.Green;
                UiTimer.Enabled = true;//断开总线 成功后，UiTimer计时器停止工作
            }
        }

        private void btnClearErrorAxisStatus_Click(object sender, EventArgs e)//清除错误轴状态
        {
            short Ret = ecat_motion.M_ClrSts(selectAxis, 1, 0);
            AddLog("清除错误轴状态", Ret);
        }       

        #region 设置&获取 选中轴 的运动参数
        private void btnGetParamters_Click(object sender, EventArgs e)//获取 选中轴 的点位运动参数并在UI控件上显示（共9个参数）
        {
            short Ret = 0;//声明变量接收方法返回值

            //获取轴参数 位置软极限
            Ret += ecat_motion.M_GetSoftLimit(selectAxis, out PositiveSoftLimit, out NegativeSoftLimit, CardID);
            nudPositiveSoftLimit.Value = PositiveSoftLimit;//获取到选中轴的 负向软极限  
            nudNegativeSoftLimit.Value = NegativeSoftLimit;//获取到选中轴的 正向软极限

            //获取轴参数 到位判断，设置以后INP有效
            Ret += ecat_motion.M_GetAxisBand(selectAxis, out InpBand, out InpTime, CardID);
            nudInpBand.Value = InpBand;//获取到选中轴的 到位位置带设定值
            nudInpTime.Value = InpTime;//获取到选中轴的 到位稳定时间设定值            

            //获取轴参数 停止减速度
            Ret += ecat_motion.M_GetStopDec(selectAxis, out StopDec, out EmgDec, CardID);
            nudStopDec.Value = (decimal)StopDec;//获取到选中轴的 停止(平滑)减速度
            nudEmgDec.Value = (decimal)EmgDec;  //获取到选中轴的 急停减速度

            //获取加速度、减速度和加加速度时间（sf）
            Ret += ecat_motion.M_GetMove(selectAxis, out CmdPrm, CardID);
            nudAcc.Value = Convert.ToDecimal(CmdPrm.acc); //获取到选中轴的 点位运动加速度
            nudDec.Value = Convert.ToDecimal(CmdPrm.dec); //获取到选中轴的 点位运动减速度
            nudSf.Value = Convert.ToDecimal(CmdPrm.sTime);//获取到选中轴的 点位运动加加速时间

            //获取轴状态是否同步，0为不同步，1为同步
            short on = 0;
            Ret += ecat_motion.M_GetAxisStsSync(selectAxis,ref on, CardID);
            nudAxisStsSync.Value = on;

            AddLog("获取轴"+selectAxis+"运动参数",Ret);
        }

        private void btnSetParamters_Click(object sender, EventArgs e)//在UI控件上设置 选中轴 的点位运动参数（共9个参数）
        {
            short Ret = 0;//声明变量接收方法返回值

            //设置轴参数 设置正负软极限（2个参数）
            PositiveSoftLimit = (int)nudPositiveSoftLimit.Value;//正向软极限
            NegativeSoftLimit = (int)nudNegativeSoftLimit.Value;//负向软极限         
            Ret = ecat_motion.M_SetSoftLimit(selectAxis,PositiveSoftLimit,NegativeSoftLimit,CardID);

            //设置INP到位判断，设置以后INP有效（2个参数）
            InpBand = (uint)nudInpBand.Value;//到位位置带设定，单位脉冲(一般给 10)
            InpTime = (uint)nudInpTime.Value;//到位稳定时间设定，单位 ms(一般给 100)
            Ret += ecat_motion.M_SetAxisBand(selectAxis,InpBand,InpTime,CardID);

            //设置轴参数 停止急停减速度（2个参数）
            StopDec = (double)nudStopDec.Value;//停止(平滑)减速度
            EmgDec = (double)nudEmgDec.Value;  //急停减速度
            Ret += ecat_motion.M_SetStopDec(selectAxis,StopDec,EmgDec,CardID);

            //设置单轴运动的加速度、减速度以及加加速时间（3个参数）
            //CmdPrm结构体已在“全局变量”里的“点位运动参数”里实例过
            CmdPrm.acc = (double)nudAcc.Value;//点位运动 加速度
            CmdPrm.dec = (double)nudDec.Value;//点位运动 减速度
            CmdPrm.sTime = (short)nudSf.Value;//点位运动 加加速时间(一般给 1 )
            Ret += ecat_motion.M_SetMove(selectAxis, ref CmdPrm,CardID);

            //设置轴状态是否同步，0为不同步，1为同步
            Ret += ecat_motion.M_SetAxisStsSync(selectAxis,(short)nudAxisStsSync.Value,CardID);

            AddLog("设置轴" + selectAxis + "运动参数", Ret);
        }
        #endregion

        #region 使能&去使能
        private void btnServoOn_Click(object sender, EventArgs e)//为 选中轴 上使能
        {
            short Ret = ecat_motion.M_Servo_On( selectAxis , CardID);
            AddLog("轴" + selectAxis + "上使能", Ret);
        }

        private void btnServoOff_Click(object sender, EventArgs e)//为 选中轴 去使能
        {
            short Ret = ecat_motion.M_Servo_Off( selectAxis , CardID);
            AddLog("轴" + selectAxis + "去使能", Ret);
        }
        #endregion

        #region 回零运动
        private void btnGetHomeParamters_Click(object sender, EventArgs e)//获取 选中轴 的回零参数（共5个参数）
        {
            //获取选中轴的回零参数（共5个）
            short Ret = ecat_motion.M_GetHomingPrm(selectAxis, ref HomeMode, ref OffSet, ref Speed1, ref Speed2, ref HomeAcc, ref ProbeFunction, 0);//获取 选中轴 的回零参数            
            nudSpeed1.Value = Speed1;     //控件显示 选中轴的 第1回零速度
            nudSpeed2.Value = Speed2;     //控件显示 选中轴的 第2回零速度
            nudHomeAcc.Value = HomeAcc;   //控件显示 选中轴的 回零加速度
            nudOffSet.Value = OffSet;     //控件显示 选中轴   回零完成后的偏移距离
            nudHomeMode.Value = HomeMode; //控件显示 选中轴的 回零模式

            AddLog("获取轴" + selectAxis + "回零参数", Ret);
        }

        private void btnSetHomeParamters_Click(object sender, EventArgs e)//设置 选中轴 的回零参数（共5个参数）
        {
            //设置选中轴的回零参数（共5个）
            HomeAcc = (uint)nudHomeAcc.Value;    //从控件拿到 回零加速度
            HomeMode = (short)nudHomeMode.Value; //从控件拿到 回零模式
            OffSet = (int)nudOffSet.Value;       //从控件拿到 回零完成后的偏移距离（如果此变量赋值为 0 表示偏移距离不做更改，即写0无效）
            Speed1 = (uint)nudSpeed1.Value;      //从控件拿到 第一回零速度
            Speed2 = (uint)nudSpeed2.Value;      //从控件拿到 第二回零速度 
            short Ret = ecat_motion.M_SetHomingPrm(selectAxis, HomeMode, OffSet, Speed1, Speed2, HomeAcc, 0);//设置 选中轴 的回零参数

            AddLog("设置轴" + selectAxis + "回零参数", Ret);
        }

        private void btnStartHoming_Click(object sender, EventArgs e)//选中轴 开始回零
        {
            short HomeStatus = 0;
            short Ret = Ec6000_HomeMove.M60_HomMove(selectAxis, HomeMode, ref HomeStatus, OffSet, Speed1, Speed2, HomeAcc, CardID);
            if (Ret == 0)
            {
                switch (HomeStatus)
                {
                    case -1:
                        AxisHomeStatus[selectAxis - 1] = "轴" + selectAxis + "回零错误";
                        break;
                    case 1:
                        AxisHomeStatus[selectAxis - 1] = "轴" + selectAxis + "正在回零";
                        break;
                    case 2:
                        AxisHomeStatus[selectAxis - 1] = "轴" + selectAxis + "未获取到";
                        break;
                    case 3:
                        AxisHomeStatus[selectAxis - 1] = "轴" + selectAxis + "回零成功";
                        break;
                    case 4:
                        AxisHomeStatus[selectAxis - 1] = "轴" + selectAxis + "回零中断";
                        break;
                }

                Task task = new Task(() =>
                {
                    Ec6000_HomeMove.M60_WaitHoming(selectAxis, ref HomeStatus, CardID);
                    switch (HomeStatus)
                    {
                        case -1:
                            AxisHomeStatus[selectAxis - 1] = "轴" + selectAxis + "回零错误";
                            break;
                        case 1:
                            AxisHomeStatus[selectAxis - 1] = "轴" + selectAxis + "正在回零";
                            break;
                        case 2:
                            AxisHomeStatus[selectAxis - 1] = "轴" + selectAxis + "未获取到";
                            break;
                        case 3:
                            AxisHomeStatus[selectAxis - 1] = "轴" + selectAxis + "回零成功";
                            break;
                        case 4:
                            AxisHomeStatus[selectAxis - 1] = "轴" + selectAxis + "回零中断";
                            break;
                    }
                    Action<string, short> action = (x, y) => AddLog(x, y);
                    lstLog.Invoke(action, "轴" + selectAxis + "回零", Ret);
                });
                task.Start();
            }
            else {AddLog("轴" + selectAxis + "回零", Ret);}
        }

        private void btnStopHoming_Click(object sender, EventArgs e)//选中轴 停止（取消）回零
        {
            short HomeStatus = 0;
            short ret = Ec6000_HomeMove.M60_StopHome( selectAxis, ref HomeStatus, CardID);            
        }
        #endregion

        #region 绝对、相对运动&停止运动
        private void btnAbsMoveStart_Click(object sender, EventArgs e)//开始绝对运动
        {
            short Ret = ecat_motion.M_AbsMove(selectAxis,(int)nudTargetPostion.Value,(int)nudAbs_TargetVelocity.Value, CardID);
            AddLog("轴" + selectAxis + "开始绝对运动",Ret);
            if (Ret == 0)
            {
                Task task = new Task(() =>
                {
                    do//因为执行机构存在滞后性，所以判断运动完成建议使用 do while 结构
                    {
                        Thread.Sleep(100);
                    }
                    while ((ServoSts[selectAxis - 1] & 0x400) == 0x400);//只判断motion信号，当motion信号为0时即认为运动完成
                    //或者⬇
                    //while ((ServoSts[SelectAxis - 1] & 0x400) == 0x400 || (ServoSts[SelectAxis - 1] & 0x800) == 0);//（也可以）同时判断motion和inp信号，当motion信号为0和inp信号为1时即认为运动完成。使用此判断条件：运动参数变量Band和Stime都不等于0。

                    Action action = () => lstLog.Items.Add("轴" + selectAxis + "绝对运动完成");
                    lstLog.Invoke(action);
                });
                task.Start();
            }            
        }

        private void btnRelMoveStart_Click(object sender, EventArgs e)//开始相对运动
        {
            short Ret = ecat_motion.M_RelMove(selectAxis,(int)nudRelativeDistance.Value,(int)nudRel_TargetVelocity.Value, CardID);
            AddLog("轴" + selectAxis + "开始相对运动", Ret);
            if (Ret == 0)
            {
                Task task = new Task(() =>
                {
                    do//因为执行机构存在滞后性，所以判断运动完成建议使用 do while 结构
                    {
                        Thread.Sleep(100);
                    }
                    while ((ServoSts[selectAxis - 1] & 0x400) == 0x400);//只判断motion信号，当motion信号为0时即认为运动完成
                    //或者⬇
                    //while ((ServoSts[SelectAxis - 1] & 0x400) == 0x400 || (ServoSts[SelectAxis - 1] & 0x800) == 0);//（也可以）同时判断motion和inp信号，当motion信号为0和inp信号为1时即认为运动完成。使用此判断条件：运动参数变量Band和Stime都不等于0。

                    Action action = () => lstLog.Items.Add("轴" + selectAxis + "相对运动完成");
                    lstLog.Invoke(action); 
                });
                task.Start();
            }            
        }

        private void btnSelectAxisStopSmoothly_Click(object sender, EventArgs e)//选中轴停止运动（平滑方式）
        {
            short Ret = ecat_motion.M_StopSingleAxis(selectAxis,0,CardID);//option为停止运动的方式: 0 表示采用平滑减速度停止，1 表示采用急停减速度停止
            AddLog("停止运动(平滑)", Ret);
            //使用下面的“ecat_motion.M_Stop”方法也可以，其详细用法请参考函数手册
            //short Ret = ecat_motion.M_Stop(Convert.ToUInt32(1 << (selectAxis - 1)),0, CardID);            
        }

        private void btnSelectAxisStopEmg_Click(object sender, EventArgs e)//选中轴停止运动（急停方式）
        {
            short Ret = ecat_motion.M_StopSingleAxis(selectAxis, 1, CardID);//option为停止运动的方式: 0 表示采用平滑减速度停止，1 表示采用急停减速度停止
            AddLog("停止运动(急停)", Ret);
            //使用下面的“ecat_motion.M_Stop”方法也可以，其详细用法请参考函数手册
            //short Ret = ecat_motion.M_Stop(Convert.ToUInt32(1 << (selectAxis - 1)),1, CardID);
        }
        #endregion

        #region Jog运动
        private void btnForwardJog_MouseDown(object sender, MouseEventArgs e)//开始Jog+运动
        {
            short Ret = 0;
            Ret = ecat_motion.M_Jog(selectAxis, (int)nudJogVelocity.Value, CardID);
            AddLog("开始Jog+运动", Ret);
        }

        private void btnForwardJog_MouseUp(object sender, MouseEventArgs e)//停止Jog+运动
        {
            short Ret = ecat_motion.M_StopSingleAxis(selectAxis, 0, CardID);//option为停止运动的方式: 0 表示采用平滑减速度停止，1 表示采用急停减速度停止
            AddLog("停止Jog+运动", Ret);
        }
        
        private void btnReverseJog_MouseDown(object sender, MouseEventArgs e)//开始Jog-运动
        {
            short Ret = ecat_motion.M_Jog(selectAxis, (int)nudJogVelocity.Value * -1, CardID);//Jog的速度给负值就会执行反向Jog运动
            AddLog("开始Jog-运动", Ret);
        }

        private void btnReverseJog_MouseUp(object sender, MouseEventArgs e)//停止Jog-运动
        {
            short Ret = ecat_motion.M_StopSingleAxis(selectAxis, 0, CardID);//option为停止运动的方式: 0 表示采用平滑减速度停止，1 表示采用急停减速度停止
            AddLog("停止Jog-运动", Ret);
        }
        #endregion        

        #region 插补运动
        private void Button_StarInterpolation_Click(object sender, EventArgs e)//开始插补运动
        {
            short dimension = (short)NumericUpDown_Dimension.Value;                   //插补维度（参与插补运动轴的数量，最小为2，最大为6）
            InterpolationAxis = new short[dimension];                                 //声明参与插补运动的轴数组,其长度等于参与插补运动轴的数量
            Position = new int[dimension];                                            //声明轴目标位置数组，其长度等于参与插补运动轴的数量，并且其每个元素和InterpolationAxis[]里的每个元素一一对应
            double acc = (double)nudAcc.Value;                             //参与插补运动轴的加速度
            double vel = (double)NumericUpDown_Vel.Value;                             //参与插补运动轴的最大速度    
            
            InterpolationAxis[0] = (short)NumericUpDown_Axis_A.Value;                 //A轴对应的轴号
            Position[0] = (int)NumericUpDown_A_Pos.Value;                             //A轴对应轴号的目标位置
            InterpolationAxis[1] = (short)NumericUpDown_Axis_B.Value;                 //B轴对应的轴号
            Position[1] = (int)NumericUpDown_B_Pos.Value;                             //B轴对应轴号的目标位置
            if (dimension >= 3)//如果插补维度>=3
            {
                InterpolationAxis[2] = (short)NumericUpDown_Axis_C.Value;             //C轴对应的轴号
                Position[2] = (int)NumericUpDown_C_Pos.Value;                         //C轴对应轴号的目标位置
                if (dimension >= 4)//如果插补维度>=4
                {
                    InterpolationAxis[3] = (short)NumericUpDown_Axis_D.Value;         //获取D轴对应的轴号
                    Position[3] = (int)NumericUpDown_D_Pos.Value;                     //获取D轴对应轴号的目标位置
                    if (dimension >= 5)//如果插补维度>=5
                    {
                        InterpolationAxis[4] = (short)NumericUpDown_Axis_E.Value;     //获取E轴对应的轴号
                        Position[4] = (int)NumericUpDown_E_Pos.Value;                 //获取E轴对应轴号的目标位置
                        if (dimension == 6) //如果插补维度==6
                        {
                            InterpolationAxis[5] = (short)NumericUpDown_Axis_F.Value; //获取F轴对应的轴号
                            Position[5] = (int)NumericUpDown_F_Pos.Value;             //获取F轴对应轴号的目标位置
                        }
                    }
                }
            }

            short Ret = ecat_motion.M_Line_All(dimension, ref InterpolationAxis[0], ref Position[0], acc, vel, 0);//开始插补运动
            AddLog("开始插补运动", Ret);

            Task task = new Task(() =>
            {                
                for (int i = 0;i < dimension;i++)
                {
                    int Sts = 0;
                    do
                    {
                        Thread.Sleep(100);
                        ecat_motion.M_GetSts(InterpolationAxis[i], out Sts, 1, 0);//获取所有参与插补运动轴中的某个轴的轴状态
                    } while ((Sts & 0x400) == 0x400);//判断轴是否停止运动，sts的 bit 10 = 1则轴为运动状态，bit 10 = 0 则轴为静止状态
                }

                //(ServoSts[SelectAxis - 1] & 0x400) == 0x400

                Action<int> action = (x) =>
                {
                    AddLog("插补运动完成");
                };
                lstLog.Invoke(action, 0);
            });
            task.Start();            
        }

        private void Button_StopInterpolation_Click(object sender, EventArgs e) //停止插补运动
        {
            uint sum = 0;
            foreach (short list in InterpolationAxis)
            {
                sum += 2^(uint)(list - 1);
            }
            short Ret = ecat_motion.M_Stop(sum, uint.MaxValue, 0);
            AddLog("停止插补运动", Ret);
        }
        #endregion

        #region 连续插补运动（案例：2轴画跑道）
        private void Button_ContinueInterpolationInitial_Click(object sender, EventArgs e)//初始化
        {
            crdCfg.dimension = 2;         //插补维度

            crdCfg.axis = new short[8];   //为了数据对齐，所以该数组的长度为8，用户在使用时只需将参与连续插补的轴号放在crdCfg.axis[]的前4个元素里就行了，后4个元素是无效的         
            crdCfg.axis[0] = 1;           //第一个轴对应的轴号为轴1
            crdCfg.axis[1] = 2;           //第二个轴对应的轴号为轴2
            //crdCfg.axis[2] = 3;         //第三个轴对应的轴号（因为前面crdCfg.dimension = 2，所以crdCfg.axis[2]和crdCfg.axis[3]无效，不需理会）
            //crdCfg.axis[3] = 4;         //第四个轴对应的轴号

            crdCfg.orignPos = new int[8]; //为了数据对齐，所以该数组的长度为8，用户在使用时只需将参与连续插补的轴号放在crdCfg.orignPos[]的前4个元素里就行了，后面4个元素是无效的 
            for (int i = 0; i < crdCfg.dimension; i++)
            {
                crdCfg.orignPos[i] = 0;//起点坐标清零
            }

            crdCfg.evenTime = 10;                                             //最小匀速时间：使每段插补能够强制匀速运动的最小时间，若速度曲线只有加速度段和减速段，可以在尖峰处形成匀速的水平直线，减小速度突变带来的冲击，单位cycletime（默认 1ms）
            crdCfg.synVelMax = 100000;                                        //坐标系的最大限制插补速度：分段速度大于该速度会默认按该速度运动，单位 pulse/S
            crdCfg.synAccMax = 1000000;                                       //坐标系的最大限制插补加速度：分段加速度大于该加速度会默认按该加速度运动，单位 pulse/S/S                                                                                                      
            crdCfg.synDecSmooth = 1000000;                                    //插补平滑停止减速度：单位 pulse/S/S
            crdCfg.synDecAbrupt = 10000000;                                   //插补紧急停止减速度：单位 pulse/S/S
            short Ret = ecat_motion.M_SetCrd(crd_1, ref crdCfg, 0);  //设置连续插补运动参数
            AddLog("初始化坐标系", Ret);

            Ret = ecat_motion.M_CrdClear(crd_1, 1, fifo_0, 0);
            AddLog("清空缓存", Ret);

            //检查是否开启“速度前瞻”功能
            if (CBox_lookahead.Checked == true)
            {
                Ret = ecat_motion.M_SetVelPlanning(crd_1, fifo_0, (short)Num_LookAheadT.Value, crdCfg.synAccMax, 100, ref asv[0], 0);
                AddLog("开启前瞻功",Ret);
            }
        }

        private void Button_SetContinueInterpolationBuffer_Click(object sender, EventArgs e)//压入点位和do点
        {
            //压入直线插补坐标 A
            int[] pos = { 0, 0 };
            short Ret = ecat_motion.M_Line(crd_1, crdCfg.dimension, ref crdCfg.axis[0], ref pos[0], 10000, 100000, 0, fifo_0, 0);
            AddLog("压入点位A", Ret);          

            //压入直线插补坐标 B1
            pos = new int[] { 10000, 10000 };
            Ret = ecat_motion.M_Line(crd_1, crdCfg.dimension, ref crdCfg.axis[0], ref pos[0], 10000, 100000, 0, fifo_0, 0);
            AddLog("压入点位B1", Ret);

            //压入do点,在“坐标B1”处将do1 置 1
            Ret = ecat_motion.M_BufIO(crd_1, 1, 1, fifo_0, 0);
            AddLog("压入do1点", Ret);

            //压入以圆弧终点和半径压入圆弧 C
            pos = new int[] { 10000, 20000 };
            Ret = ecat_motion.M_Arc2R(crd_1, ref crdCfg.axis[0], ref pos[0], 5000, 0, 10000, 100000, fifo_0, 0);
            AddLog("压入点位C", Ret);

            //压入直线插补坐标 D
            pos = new int[] { 30000, 20000 };
            Ret = ecat_motion.M_Line(crd_1, crdCfg.dimension, ref crdCfg.axis[0], ref pos[0], 10000, 100000, 0, fifo_0, 0);
            AddLog("压入点位D", Ret);

            //压入以圆弧终点和半径压入圆弧 E
            pos = new int[] { 30000, 10000 };//终点
            Ret = ecat_motion.M_Arc2R(crd_1, ref crdCfg.axis[0], ref pos[0], 5000, 0, 10000, 100000, fifo_0, 0);
            AddLog("压入点位E", Ret);

            //压入do点,在“坐标B1”处将do1 置 0
            Ret = ecat_motion.M_BufIO(crd_1, 1, 0, fifo_0, 0);
            AddLog("压入do1点", Ret);

            //压入直线插补坐标 B2
            pos = new int[] { 10000, 10000 };
            Ret = ecat_motion.M_Line(crd_1, crdCfg.dimension, ref crdCfg.axis[0], ref pos[0], 10000, 100000, 0, fifo_0, 0);
            AddLog("压入点位B2", Ret);
        }

        private void Button_StartContinueInterpolation_Click(object sender, EventArgs e)//开始 连续插补运动
        {
            //检查是否开启“速度前瞻”功能
            if (CBox_lookahead.Checked == true)
            {
                short Ret1 = lctdevice.ecat_motion.M_CrdData(crd_1, ref asv[0], fifo_0, 0);
                AddLog("推送数据", Ret1);
            }

            short Ret2 = ecat_motion.M_CrdStart(crd_1, fifo_0, 0);//使对应坐标系开始运行连续插补运动            
            AddLog("连续插补运动", Ret2);

            Task task = new Task(() =>
            {
                do
                {
                    Thread.Sleep(10);
                    //获取插补状态
                    Ret2 = ecat_motion.M_CrdStatus(crd_1, out sSts, out sCmdNum, out sSpace, fifo_0, 0);//获取缓冲区运行状态                    
                }
                while (sSts == 1 || sCmdNum != 0);//判断插补数量为0并且插补状态结束
                Action<int> action = (x) =>
                {
                    AddLog("连续插补运动完成");
                };
                lstLog.Invoke(action, 0);

                //关闭插补功能:连续插补运动完成后，要把连续插补的功能关闭（⬇下面的代码）
                Thread.Sleep(100);
                Ret2 = ecat_motion.M_CrdStop(crd_1, fifo_0, 0);//使对应坐标系停止连续插补运动
                while (true)
                {
                    System.Threading.Thread.Sleep(10);
                    ecat_motion.M_CrdStatus(crd_1, out short psts, out short pcmdnum, out int space, fifo_0, 0); //获取缓冲区运行状态
                    if (psts != 1) //插补运动状态( 0：不在运动，1：正在运动 )
                        break;
                }                
            });
            task.Start();
        }

        private void Button_StopContinueInterpolation_Click(object sender, EventArgs e)//停止 连续插补运动
        {
            short Ret = ecat_motion.M_CrdStop(crd_1, fifo_0, 0);
            while (true)
            {
                Thread.Sleep(10);
                ecat_motion.M_CrdStatus(crd_1, out  short psts, out short pcmdnum, out int space, fifo_0, 0);//获取缓冲区运行状态
                if (psts != 1)                                                                               //插补运动状态( 0：不在运动，1：正在运动 )
                    break;
            }
            Ret = ecat_motion.M_CrdClear(crd_1, 1, fifo_0, 0);
            AddLog("停止连续插补运动", Ret);
        }

        private void Button_PauseContinueInterpolation_Click(object sender, EventArgs e)//暂停 连续插补运动
        {
            short Ret = ecat_motion.M_Get_Digital_Chn_Output(1, out Do1Value, 0);//暂停之前首先获取一下do1的输出状态
            Ret = ecat_motion.M_CrdStop(crd_1, fifo_0, 0);
            while (true)
            {
                Thread.Sleep(10);
                ecat_motion.M_CrdStatus(crd_1, out short psts, out short pcmdnum, out int space, fifo_0, 0);//获取缓冲区运行状态
                if (psts != 1)                                                                              //插补运动状态( 0：不在运动，1：正在运动 )
                    break;
            }
            lctdevice.ecat_motion.M_Set_Digital_Chn_Output(1, 0, 0);
            AddLog("暂停插补", Ret);
        }

        private void Button_ContinueContinueInterpolation_Click(object sender, EventArgs e)//继续 连续插补运动
        {
            short Ret = ecat_motion.M_Set_Digital_Chn_Output(1, Do1Value, 0);              //do1输出“Do1Valu”（Do1Valu在暂停之前获取）
            Ret = ecat_motion.M_CrdStart(crd_1, fifo_0, 0);                                //使对应坐标系开始运行连续插补运动
            AddLog("继续插补", Ret);            
        }
        #endregion

        #region 电子齿轮
        private void Button_SetGear_Click(object sender, EventArgs e)//设置电子齿轮参数
        {
            short master = Convert.ToInt16(NumericUpDown_GearMasterAxis.Value);//主轴轴号
            short slaveA = Convert.ToInt16(NumericUpDown_GearSlaveAxisA.Value);  //从轴1的轴号
            short slaveB = Convert.ToInt16(NumericUpDown_GearSlaveAxisB.Value);  //从轴1的轴号
            short mastertype = 1; //2: 主轴规划位置
                                  //1：主轴编码器反馈
                                  //4：主轴规划输出值
                                  //5：主轴编码器输出值

            int mastereven = Convert.ToInt32(NumericUpDown_MasterEven.Value);  //主轴的齿轮比
            int slaveeven1 = Convert.ToInt32(NumericUpDown_SlaveEvenA.Value);    //从轴1的齿轮比
            int slaveeven2 = Convert.ToInt32(NumericUpDown_SlaveEvenB.Value);    //从轴2的齿轮比

            if ((ServoSts[slaveA - 1] & 0x400) != 0x400)//判断从轴1是否空闲，从轴1在运动状态下无法进行电子齿轮操作
            {
                short Ret = ecat_motion.M_Gear(slaveA, 0, 0);//将从轴切换到电子齿轮模式
                Ret = ecat_motion.M_SetGearMaster(slaveA, master, mastertype, 0, 0);//设置主从轴，以及从轴跟随对象
                Ret = ecat_motion.M_SetGearRatio(slaveA, mastereven, slaveeven1, 0, 0);
                AddLog("开启轴" + slaveA + "电子齿轮," + "跟随轴" + master, Ret);
            }
            else
            {
                MessageBox.Show("从轴1正在运动，请检查各个轴状态");
            }

            if ((ServoSts[slaveB - 1] & 0x400) != 0x400)//判断从轴2是否空闲，从轴2在运动状态下无法进行电子齿轮操作
            {
                short Ret = ecat_motion.M_Gear(slaveB, 0, 0);//将从轴切换到电子齿轮模式
                Ret = ecat_motion.M_SetGearMaster(slaveB, master, mastertype, 0, 0);//设置主从轴，以及从轴跟随对象
                Ret = ecat_motion.M_SetGearRatio(slaveB, mastereven, slaveeven2, 0, 0);
                AddLog("开启轴" + slaveB + "电子齿轮," + "跟随轴" + master, Ret);
            }
            else
            {
                MessageBox.Show("从轴2正在运动，请检查各个轴状态");
            }
        }

        private void Button_GetGear_Click(object sender, EventArgs e)//获取电子齿轮参数
        {
            short slaveA = Convert.ToInt16(NumericUpDown_GearSlaveAxisA.Value);
            short Ret = ecat_motion.M_GetGearMaster(slaveA, out short master, out short mastertype, out short masteritem, 0);
            Ret += ecat_motion.M_GetGearRatio(slaveA, out int mastereven, out int slaveeven, out int slope, 0);
            TextBox_GetGearParam1.Text = "轴" + slaveA + "（从轴）绑定主轴" + master + "（主轴）。齿轮比为：" + mastereven + "(主)/" + slaveeven+"(从轴)";

            short slaveB = Convert.ToInt16(NumericUpDown_GearSlaveAxisB.Value);
            Ret += ecat_motion.M_GetGearMaster(slaveB, out short Master, out short Mastertype, out short Masteritem, 0);
            Ret += ecat_motion.M_GetGearRatio(slaveB, out int Mastereven, out int Slaveeven, out int Slope, 0);
            AddLog("获取电子齿轮参数", Ret);
            TextBox_GetGearParam2.Text = "轴" + slaveB + "（从轴）绑定轴" + Master + "（主轴）。齿轮比为：" + Mastereven + "(主)/" + Slaveeven+"(从轴)";
        }

        private void Button_StartGear_Click(object sender, EventArgs e)//开启电子齿轮功能
        {
            short slaveA = Convert.ToInt16(NumericUpDown_GearSlaveAxisA.Value);                  
            short Ret = ecat_motion.M_GearStart(Convert.ToUInt16(1 << (short)(slaveA - 1)), 0);

            short slaveB = Convert.ToInt16(NumericUpDown_GearSlaveAxisB.Value);
            Ret += ecat_motion.M_GearStart(Convert.ToUInt16(1 << (short)(slaveB - 1)), 0);

            AddLog("开启电子齿轮功能", Ret);
        }

        private void Button_CancelGear_Click(object sender, EventArgs e)//关闭电子齿轮功能
        {
            short slaveA = Convert.ToInt16(NumericUpDown_GearSlaveAxisA.Value);
            short Ret = ecat_motion.M_Stop(Convert.ToUInt32(1 << (short)(slaveA - 1)), 0, 0);

            short slaveB = Convert.ToInt16(NumericUpDown_GearSlaveAxisB.Value);
            Ret += ecat_motion.M_Stop(Convert.ToUInt32(1 << (short)(slaveB - 1)), 0, 0);

            AddLog("轴"+ slaveA+"和轴"+ slaveB + "取消电子齿轮功能", Ret);
        }
        #endregion

        #region IO控制：输出部分（另外输入部分在“IoStatusLoop()”方法里）
        private void Button_SetDo1To32_Click(object sender, EventArgs e)//设置 数字量输出1→32通道 的输出值
        {
            ecat_motion.M_Set_Digital_Port_Output(1, (uint)NumericUpDown_Do1To32SetValue.Value, uint.MaxValue, 0);
        }

        private void Button_SetDo1_Click(object sender, EventArgs e)//设置 数字量输出通道1 的输出值
        {
            short Ret = ecat_motion.M_Set_Digital_Chn_Output(1, (short)NumericUpDown_Do1SetValue.Value, 0);//设置 数字量输出通道1 的输出值
        }

        private void Button_SetAo1_Click(object sender, EventArgs e)//设置 模拟量输出1通道 的输出值
        {
            AoValue[0] = (short)NumericUpDown_Ao1SetValue.Value;     //从控件拿到 拟量输出通道1 的设定值            
            short Ret = ecat_motion.M_Set_Analog_Output(1, ref AoValue[0], 1, 0);//设置 模拟量输出1通道 的输出值
            AddLog("设置模拟量输出1通道",Ret);
        }
        #endregion       

        #region 位置比较触发

        #region 点表触发
        private void Button_InitialPointTableTrigger_Click(object sender, EventArgs e)//点表触发初始化
        {
            //1、将 第1个CMP模块的 输出通道模式 设置为 位置比较触发
            uint Mode = 1;         //设置触发通道的模式（0为通用DO，1为位置比较触发，2为PWM输出）            
            Mini_Cmp_Lib.TriggerOut_SetOutMode(CardID, CmpNo, Mode);

            //2、将 第1个CMP模块的触发输出通道0 的输出脉宽设置为1ms
            int TriggerOutNumber0 = 0; //每个CMP模块共4个触发输出通道编号（0-3），这里使用的是 触发输出通道0
            uint Width = 100000;       //注：脉宽的单位为10ns。
            Mini_Cmp_Lib.TriggerOut_SetPulseWidth(CardID, CmpNo, TriggerOutNumber0, Width);

            //3、不将 任何比较器 绑定到 触发输出通道0 （相当于对触发输出通道0做了一个重置操作）
            uint LineCompareMask = 0;//线性比较器掩码
            uint PreCompareMask = 0; //预设定比较器掩码
            uint PulsePolarity = 0;  //触发输出通道的输出极性（0为不取反， 1为取反）
            Mini_Cmp_Lib.TriggerOut_SetParam(CardID, CmpNo, TriggerOutNumber0, LineCompareMask, PreCompareMask, PulsePolarity);

            //4、将 预设定比较器0 绑定到 触发输出通道0
            PreCompareMask = 1;//“1”对应PreCompareMask参数的 bit0 位
            Mini_Cmp_Lib.TriggerOut_SetParam(CardID, CmpNo, TriggerOutNumber0, LineCompareMask, PreCompareMask, PulsePolarity);

            //5、将 编码器输入通道0 绑定到 预设定比较器0
            int EncoderNo0 = 0;    //每个CMP模块共4个编码器输入编号（0-3）,这里使用的是 编码器输入通道0
            int PreCompareNo0 = 0; //预设定比较器编号（0-3），这里使用的是预设定比较器0
            Mini_Cmp_Lib.PreCmp_BindingEncoder(CardID, CmpNo, EncoderNo0, PreCompareNo0); 

            //6、重置 预设定比较器0 的触发数据
            Mini_Cmp_Lib.PreCmp_ResetTrigData(CardID, CmpNo, PreCompareNo0);

            //7、将 预设定比较器0 的触发方向设置为正向触发
            int Dir = 0;// Dir的取值可以为0、1和2，其中0为正向触发，1为反向触发，2为双向触发
            Mini_Cmp_Lib.PreCmp_SetTrigDir(CardID, CmpNo, PreCompareNo0, Dir);

            //8、向 预设定比较器0 内压入点表数据
            int[] PointTablePositions = { 1000, 2000, 4000, 8000, 16000, 32000};
            Mini_Cmp_Lib.PreCmp_SetTrigData(CardID, CmpNo, PreCompareNo0, PointTablePositions);
        }

        private void Button_TurnOffTrigger1_Click(object sender, EventArgs e)//关闭点表触发功能
        {
            uint PreCompareNo0 = 0;//预设定比较器0
            Mini_Cmp_Lib.PreCmp_SetEnable( CardID, CmpNo, PreCompareNo0, 0);//为预设定比较器0下使能（“0”为上使能）
        }

        private void Button_TurnOnTrigger1_Click(object sender, EventArgs e)//开启点表触发功能
        {
            uint PreCompareNo0 = 0; 
            Mini_Cmp_Lib.PreCmp_SetEnable(CardID, CmpNo, PreCompareNo0, 1);//为预设定比较器0上使能（“1”为上使能）
        }
        #endregion

        #region 线性触发 
        private void Button_InitialLinearTrigger_Click(object sender, EventArgs e)//线性触发初始化
        {
            //1、将 第1个CMP模块的 输出通道模式 设置为 位置比较触发
            uint Mode = 1;         //设置触发通道的模式（0为通用DO，1为位置比较触发，2为PWM输出）            
            Mini_Cmp_Lib.TriggerOut_SetOutMode(CardID, CmpNo, Mode);

            //2、将 第1个CMP模块的触发输出通道0 的输出脉宽设置为1ms
            int TriggerOutNumber0 = 0; //每个CMP模块共4个触发输出通道编号（0-3），这里使用的是 触发输出通道0
            uint Width = 100000;       //注：脉宽的单位为10ns。
            Mini_Cmp_Lib.TriggerOut_SetPulseWidth(CardID, CmpNo, TriggerOutNumber0, Width);

            //3、不将 任何比较器 绑定到 触发输出通道0 （相当于对触发输出通道0做了一个重置操作）
            uint LineCompareMask = 0;//线性比较器掩码
            uint PreCompareMask = 0; //预设定比较器掩码
            uint PulsePolarity = 0;  //触发输出通道的输出极性（0为不取反， 1为取反）
            Mini_Cmp_Lib.TriggerOut_SetParam(CardID, CmpNo, TriggerOutNumber0, LineCompareMask, PreCompareMask, PulsePolarity);

            //4、将 预设定比较器0 绑定到 触发输出通道0
            LineCompareMask = 1;//这里将线性定比较器的掩码赋值为1，又因为每一位对应一个预设定比较器,bit0对应预设比较器0，所以这里使用的是线性定比较器0。
            Mini_Cmp_Lib.TriggerOut_SetParam(CardID, CmpNo, TriggerOutNumber0, LineCompareMask, PreCompareMask, PulsePolarity);

            //5、将 编码器输入通道0 绑定到 线性定比较器0
            int EncoderNo0 = 0;    //编码器输入编号（0-3）,这里使用的是编码器0。
            int LineCompareNo0 = 0; //线性比较器编号（0-9），这里使用的是线性比较器0。
            Mini_Cmp_Lib.LineCmp_BingdingEncoder(CardID, CmpNo, EncoderNo0, LineCompareNo0);

            //6、设置等间距触发参数
            int StartPos = 1000;//进行线性触发的起点
            int EndPos = 10000; //进行线性触发的终点
            int Interval = 1000;//每次线性触发的间隔
            Mini_Cmp_Lib.LineCmp_SetTriggerData(CardID, CmpNo, LineCompareNo0, StartPos, EndPos, Interval);
        }

        private void Button_TurnOffTrigger2_Click(object sender, EventArgs e)//关闭线性触发功能
        {  
            uint LineCompareNo = 0;
            Mini_Cmp_Lib.LineCmp_SetEnable(CardID, CmpNo, LineCompareNo, 0); //为线性比较器0下使能（“0”为下使能）
        }

        private void Button_TurnOnTrigger2_Click(object sender, EventArgs e)//开启线性触发功能
        {
            uint LineCompareNo = 0;
            Mini_Cmp_Lib.LineCmp_SetEnable(CardID, CmpNo, LineCompareNo, 1);//为线性比较器0上使能（“1”为上使能）
        }
        #endregion

        #region 手动触发 
        private void Button_0_ManualOutput_Click(object sender, EventArgs e)
        {
            int TriggerOutNum0 = 0;
            Mini_Cmp_Lib.TriggerOut_SetManualOutput(CardID, CmpNo, TriggerOutNum0);
        }

        private void Button_1_ManualOutput_Click(object sender, EventArgs e)
        {
            int TriggerOutNum1 = 1;
            Mini_Cmp_Lib.TriggerOut_SetManualOutput(CardID, CmpNo, TriggerOutNum1);
        }

        private void Button_2_ManualOutput_Click(object sender, EventArgs e)
        {
            int TriggerOutNum2 = 2;
            Mini_Cmp_Lib.TriggerOut_SetManualOutput(CardID, CmpNo, TriggerOutNum2);
        }

        private void Button_3_ManualOutput_Click(object sender, EventArgs e)
        {
            int TriggerOutNum3 = 3;
            Mini_Cmp_Lib.TriggerOut_SetManualOutput(CardID, CmpNo, TriggerOutNum3);
        }
        #endregion

        #region 2D比较触发
        private void btnInitial2D_Click(object sender, EventArgs e)
        {
            Mini_Cmp_Lib.TriggerOut_SetOutMode(CardID, CmpNo, 1);//将触发输出模式设置为脉冲输出
            Mini_Cmp_Lib.TriggerOut_SetPulseWidth(CardID, CmpNo, 0, 100000);//设置触发输出0脉宽为1ms
            Mini_Cmp_Lib.TriggerOut_Set2DCompare(CardID, CmpNo, 0, 1);//将触发输出0绑定2d触发比较器
            Mini_Cmp_Lib._2DCmp_SetCompareSource(CardID, CmpNo, 0, 1);//将2d比较器的X轴source设置为编码器0，Y轴source设置为编码器1

            Mini_Cmp_Lib._2DCmp_SetTrigRegionWindow(CardID, CmpNo, 500);//设置触发点的WINDOW区域
            //注    意：如果X轴和Y轴的刚性不好导致误差偏大，其坐标轨迹就不会进入触发区域，从而导致漏触发。
            //解决方法：增加电机刚性或者增大“RegionWindow”参数的数值
        }

        private void btnSet2DPoints_Click(object sender, EventArgs e)//压入2D触发点位
        {
            int[] x = { 10000, 20000, 30000, 40000, 50000, 60000 };
            int[] y = { 10000, 20000, 30000, 40000, 50000, 60000 };
            Mini_Cmp_Lib._2DCmp_SetTrigPoint(CardID, CmpNo, x, y, 6);//压入2d触发点位，这里的X数组和Y数组用来模拟45°直线触发，共6个坐标点
        }

        private void btnOpen2DTriggerFunction_Click(object sender, EventArgs e)//开启 2D位置比较触发
        {
            Mini_Cmp_Lib._2DCmp_SetEnable(CardID, CmpNo, 1);//为2D比较器上使能（“1”为上使能）
        }

        private void btnClose2DTriggerFunction_Click(object sender, EventArgs e)//关闭 2D位置比较触发
        {
            Mini_Cmp_Lib._2DCmp_SetEnable(CardID, CmpNo, 0);//为2D比较器下使能（“0”为下使能）
        }
        #endregion

        #region 清零操作
        private void Button_SetEncoder_0_CurrentData_Click(object sender, EventArgs e)//将编码器输入通道0的位置清零
        {
            int EncoderNo0 = 0;//选中操作对象为触发输出通道0
            int Position0 = 0; //这里将 0 赋值给“Position0”，即将编码器输入通道0的位置清零。给“Position0”赋的值并非一定为0,也可以是用户想设置的数值
            Mini_Cmp_Lib.Encoder_SetCurrentData(CardID, CmpNo, EncoderNo0, Position0);//设置编码器输入通道0当前的位置
        }

        private void Button_SetEncoder_1_CurrentData_Click(object sender, EventArgs e)//将编码器输入通道1的位置清零
        {
            int EncoderNo1 = 1;//选中操作对象为触发输出通道1
            int Position1 = 0; 
            Mini_Cmp_Lib.Encoder_SetCurrentData(CardID, CmpNo, EncoderNo1, Position1);
        }

        private void Button_SetEncoder_2_CurrentData_Click(object sender, EventArgs e)//将编码器输入通道2的位置清零
        {
            int EncoderNo2 = 2;//选中操作对象为触发输出通道2
            int Position2 = 0; 
            Mini_Cmp_Lib.Encoder_SetCurrentData(CardID, CmpNo, EncoderNo2, Position2);
        }

        private void Button_SetEncoder_3_CurrentData_Click(object sender, EventArgs e)//将编码器输入通道3的位置清零
        {
            int EncoderNo3 = 3;//选中操作对象为触发输出通道3
            int Position3 = 0; 
            Mini_Cmp_Lib.Encoder_SetCurrentData(CardID, CmpNo, EncoderNo3, Position3);
        }

        private void Button_ResetTriggerOut_0_Count_Click(object sender, EventArgs e)//将触发输出通道0的触发计数清零
        {
            uint TriggerOutNum0 = 0;//选中操作对象为触发输出通道0
            Mini_Cmp_Lib.TriggerOut_ResetCounter(CardID, CmpNo, TriggerOutNum0);
        }

        private void Button_ResetTriggerOut_1_Count_Click(object sender, EventArgs e)//将触发输出通道1的触发计数清零
        {
            uint TriggerOutNum1 = 1;//选中操作对象为 触发输出通道1
            Mini_Cmp_Lib.TriggerOut_ResetCounter(CardID, CmpNo, TriggerOutNum1);
        }

        private void Button_ResetTriggerOut_2_Count_Click(object sender, EventArgs e)//将触发输出通道2的触发计数清零
        {
            uint TriggerOutNum2 = 2;//选中操作对象为 触发输出通道2
            Mini_Cmp_Lib.TriggerOut_ResetCounter(CardID, CmpNo, TriggerOutNum2);
        }

        private void Button_ResetTriggerOut_3_Count_Click(object sender, EventArgs e)//将触发输出通道3的触发计数清零
        {
            uint TriggerOutNum3 = 3;//选中操作对象为 触发输出通道3
            Mini_Cmp_Lib.TriggerOut_ResetCounter(CardID, CmpNo, TriggerOutNum3);
        }
        #endregion

        #endregion

        #region SDO操作
        private void Button_SdoWrite_Click(object sender, EventArgs e)//SDO写操作
        {
            short axis = decimal.ToInt16(NumericUpDown_SdoWriteAxisNum.Value);
            string index_ = "0x" + BextBox_SdoWriteIndex.Text;
            short index = Convert.ToInt16(index_, 16);
            short subindex = decimal.ToInt16(NumericUpDown_SdoWriteSubIndex.Value);
            uint data = decimal.ToUInt32(NumericUpDown_SdoWriteValue.Value);
            short datasize = decimal.ToInt16(NumericUpDown_SdoWriteDataSize.Value);
            ecat_motion.M_EcatSDOWrite(axis, index, subindex, data, datasize, CardID);
        }

        private void Button_SdoRead_Click(object sender, EventArgs e)//SDO读操作
        {
            short axis = decimal.ToInt16(NumericUpDown_SdoReadAxisNum.Value);
            string index_ = "0x" + BextBox_SdoReadIndex.Text;
            short index = Convert.ToInt16(index_, 16);
            short subindex = decimal.ToInt16(NumericUpDown_SdoReadSubIndex.Value);
            uint data = 0;
            short datasize = decimal.ToInt16(NumericUpDown_SdoReadDataSize.Value);
            ecat_motion.M_EcatSDORead(axis, index, subindex, datasize, out data, 1, CardID);
            TextBox_SdoReadValue.Text = data.ToString();
        }
        #endregion

        #region 其他（对客户不重要）
        private void NumericUpDown_SelectAxisID_ValueChanged(object sender, EventArgs e)// 选中轴的轴号 发生改变
        {
            selectAxis = (short)nudSelectAxisID.Value;
        }

        void AddLog(string context, short Ret)//添加log信息
        {
            if (Ret == 0)
            {
                lstLog.Items.Add(context + "成功");
            }
            else
            {
                lstLog.Items.Add(context + "失败:" + Ret);
            }
            lstLog.SelectedIndex = lstLog.Items.Count - 1;
        }

        void AddLog(string context)//添加log信息
        {
            lstLog.Items.Add(context);
            lstLog.SelectedIndex = lstLog.Items.Count - 1;
        }
        #endregion      

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)//关闭线程、所有轴去使能、断开总线 和 关闭卡片
        {
            try
            {
                Thread_EmgStatus.Abort(0);//关闭板卡Emg状态线程
                Thread.Sleep(100);
            }
            catch { }

            try
            {
                Thread_AxisStatus.Abort(0);//关闭轴状态线程
                Thread.Sleep(100);
            }
            catch { }

            try
            {
                Thread_IoStatus.Abort(0);//关闭IO状态线程 
                Thread.Sleep(100);
            }
            catch { }

            try
            {
                for (short i = 1; i <= ServoNum; i++)
                    ecat_motion.M_Servo_Off(i, CardID);//所有轴去使能
                Thread.Sleep(100);
            }
            catch { }

            try
            {
                ecat_motion.M_DisconnectECAT(0);//断开总线
                Thread.Sleep(100);
            }
            catch { }

            try
            {
                ecat_motion.M_Close(0);//关闭M60卡片
                Thread.Sleep(100);
            }
            catch { }
        }

        private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            Environment.Exit(0);
        }

        private void btnSetCardParamter_Click(object sender, EventArgs e)//设置板卡参数
        {
            ecat_motion.M_SetEmgInv((short)cmbEmgInv.SelectedIndex, CardID);
            switch ((short)cmbEmgAction.SelectedIndex)
            {
                case 0:
                    ecat_motion.M_SetEmgAction(0x00,CardID);//不减速，直接掉使能（默认值）
                    break;
                case 1:
                    ecat_motion.M_SetEmgAction(0x01,CardID);//以缓停减速度停机，然后掉使能
                    break;
                case 2:
                    ecat_motion.M_SetEmgAction(0x02,CardID);//以急停减速度停机，然后掉使能
                    break;
                case 3:
                    ecat_motion.M_SetEmgAction(0x11,CardID);//以缓停减速度停机，不掉使能
                    break;
                case 4:
                    ecat_motion.M_SetEmgAction(0x12,CardID);//以急停减速度停机，不掉使能
                    break;
            }
        }

        private void btnSetAxisCurrentPostion_Click(object sender, EventArgs e)//设置选中轴的当前位置
        {
            short ret = ecat_motion.M_SetCurrentPos(selectAxis,(int)nudCurrentPostion.Value,CardID);
            AddLog("设置轴"+ selectAxis+ "的当前位置", ret);
        }
    }
}
